package edu.gmu.streams;

import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * A ByteBufferInputStream read primitive Java numbers
 * and characters to an input stream in a big-endian format.
 * <p>
 * This class differs from DataInputStream in that it also
 * provides efficient methods for reading primitive arrays in.
 * <p>
 * Note: ByterBuffer "in" and the view buffers (e.g. IntBuffer)
 * keep different state - thus after reading from the IntBuffer view
 * the ByteBuffer "in"'s position has not moved, though data has 
 * certainly beed read from the array.  We adjust the position of
 * the ByteBuffer "in" appropriately after using the view buffers.
 *
 * @author  James H. Pope
 * @version $Revision:$ $Date:$
 */
public class ByteBufferInputStream implements BlockInputStream
{
    private static final byte TRUE  = 0x01;
    private static final byte FALSE = 0x00;
    
    //private int read = 0;
    private ByteBuffer in = null;
    
    /**
     * Creates a new ByteBufferInputStream and chains it to the input
     * stream specified by the in argument.
     * @param in
     */
    public ByteBufferInputStream( ByteBuffer in )
    {
        this.in = in;
    }
    
    /**
     * Advances the buffers currect position by the number of bytes indicated.
     * @param num bytes to advance
     */
    public void advance( int num )
    {
        this.in.position( this.in.position() + num );
    }
    
    /**
     * Skips the number of bytes ahead as indicated.
     * @param num bytes to skip
     */
    public void skipBytes(int skip) throws IOException
    {
        // Not so efficient - but gets the job done
        for (int i = 0; i < skip; i++)
        {
            this.in.get(); // ignore return, move position by one
        }
    }
    
    /**
     * Reads byte from underlying stream.
     * @return b
     * @throws java.io.IOException
     */
    public byte readByte() throws IOException
    {
        return this.in.get();
    }
    
    /**
     * Reads boolean (1-byte) from underlying stream.
     * @return
     * @throws java.io.IOException
     */
    public boolean readBoolean() throws IOException
    {
        return (this.in.get()==FALSE)?false:true;
    }
    
    /**
     * Reads the next two bytes from underlying stream as an short.
     * @return short
     * @throws java.io.IOException
     */
    public short readShort() throws IOException
    {
        return this.in.getShort();
    }
    
    /**
     * Reads the next four bytes from underlying stream as an int.
     * @return int
     * @throws java.io.IOException
     */
    public int readInt() throws IOException
    {
        return this.in.getInt();
    }
    
    /**
     * Reads the next eight bytes from underlying stream as a long.
     * @return long
     * @throws java.io.IOException
     */
    public long readLong() throws IOException
    {
        return this.in.getLong();
    }
    
    /**
     * Reads the next four bytes from underlying stream as a float.
     * @return float
     * @throws java.io.IOException
     */
    public float readFloat() throws IOException
    {
        return this.in.getFloat();
    }
    
    /**
     * Reads the next eight bytes from underlying stream as a double.
     * @return double
     * @throws java.io.IOException
     */
    public double readDouble() throws IOException
    {
        return this.in.getDouble();
    }
    
    /**
     * Reads variable length (2-byte length header) of UTF characters from
     * the underlying stream as a String. 
     * @return String
     * @throws java.io.IOException
     */
    public String readUTF() throws IOException
    {
        int stringLength = this.readUnsignedShort();
        byte[] bytes = new byte[stringLength];
        this.in.get(bytes);
        return new String( bytes, "UTF-8" );
    }
    
    
    /**
     * Bulk transfers bytes from this buffer into the given destination array.
     * @param bytes
     * @throws java.io.IOException
     */
    public void read( byte[] bytes ) throws IOException
    {
        this.in.get(bytes);
    }
    
    /**
     * Bulk transfers bools from this buffer into the given destination array.
     * Implemenations may fall back to loop-calling readBoolean().
     * @param booleans
     * @throws java.io.IOException
     */
    public void read( boolean[] booleans ) throws IOException
    {
        for (int i = 0; i < booleans.length; i++)
        {
            booleans[i] = this.readBoolean();
        }
    }
    
    /**
     * Bulk transfers shorts from this buffer into the given destination array.
     * @param shorts
     * @throws java.io.IOException
     */
    public void read( short[] shorts ) throws IOException
    {
        this.in.asShortBuffer().get(shorts);
        this.advance( shorts.length*2 );
    }
    
    /**
     * Bulk transfers ints from this buffer into the given destination array.
     * @param integers
     * @throws java.io.IOException
     */
    public void read( int[] integers ) throws IOException
    {
        this.in.asIntBuffer().get(integers);
        this.advance( integers.length*4 );
    }
  
    /**
     * Bulk transfers floats from this buffer into the given destination array.
     * @param floats
     * @throws java.io.IOException
     */
    public void read( float[] floats ) throws IOException
    {
        this.in.asFloatBuffer().get(floats);
        this.advance( floats.length*4 );
    }
    
    /**
     * Bulk transfers longs from this buffer into the given destination array.
     * @param longs
     * @throws java.io.IOException
     */
    public void read( long[] longs ) throws IOException
    {
        this.in.asLongBuffer().get(longs);
        this.advance( longs.length*8 );
    }
    
    /**
     * Bulk transfers doubles from this buffer into the given destination array.
     * @param doubles
     * @throws java.io.IOException
     */
    public void read( double[] doubles ) throws IOException
    {
        this.in.asDoubleBuffer().get(doubles);
        this.advance( doubles.length*8 );
    }
    
    /**
     * Bulk transfers String from this buffer into the given destination array.
     * Implemenations may fall back to looping-calling readUTF().
     * @param strings
     * @throws java.io.IOException
     */
    public void read( String[] strings ) throws IOException
    {
        for (int i = 0; i < strings.length; i++)
        {
            strings[i] = this.readUTF();
        }
    }
    
    //------------------------------------------------------------------------//
    // Utility method for readUTF
    //------------------------------------------------------------------------//
    /**
     * Reads a two byte unsigned <code>short<\code> from the underlying
     * input stream in big-endian order, high byte first.
     *
     * @return    the <code>int</code> unsigned short read.
     * @exception EOFException if the end of the underlying input stream has
     *            been reached.
     * @exception IOException  if the underlying stream throws an exception.
     */
    public int readUnsignedShort() throws IOException
    {
        // Note - regardless of endian-order, spec says always big-endian - yeh!
        int ch1 = in.get()&0xff;
        int ch2 = in.get()&0xff;
        
        if(ch1 == -1 || ch2 == -1) throw new EOFException();
        return (ch1 << 8) + ch2;
    }



} //end ByteBufferInputStream
